#!/usr/bin/env python
# encoding: utf-8

import socket
import logging
import optparse
import sys, time
import os.path, os
import parse
from select import *
from imp_protocolo import *
from constants import *
from conexion_cliente import *


""" Este modulo implementa el servidor. Se define la clase servidor y sus meto-
    dos correspondientes, siendo el mas importante server_run que pone a "co-
    rrer" el server. Se ocupa tambien de construir y enviar las respuestas en
    caso de error. En cambio, si los pedidos se procesan correctamente, las
    mismas son construidas por las propias funciones, definidas en el modulo
    imp_protocolo, pero sigue siendo el servidor quien las envia.
"""



class Server(object):
    def __init__(self, directorio, port=default_port):
        """ Crea una nueva instancia de la clase server. La nueva instancia se
            liga al puerto pasado como argumento. El directorio pasado como ar-
            gumento es el que el server va a poner a disposicion del cliente
        """
        self.sckt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sckt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # La linea de arriba es la que permite ligar enseguida un servidor al
        # puerto y no tener que esperar alrededor de un minuto
        try :
            self.sckt.bind (('', port))
        except sckt.error , descripcion:
            codigo_error = descripcion[0]
            if codigo_error==EINVAL : return (EINVAL, "el puerto ya esta usado por otro proceso") 
        try :
            self.sckt.listen (1) 
        except sckt.error , descripcion:
            codigo_error = descripcion[0]
            if codigo_error==EINVAL : return (EINVAL, "no se puede escuchar por el puerto que esta usado por otro proceso")
        self.wd = directorio
        # Diccionario de pares (fd de socket, conexion cliente)
        self.conexiones_cliente = dict()
        self.poll_object = poll() 

    def server_run(self):
        # Registramos el descriptor de archivo que se corresponde con el socket
        # a donde se conectan nuevos clientes. Indicamos que nos interesa el
        # evento POLLIN y el evento POLLERR.
        self.poll_object.register (self.sckt, POLLIN | POLLERR) 
        # Pone a correr un server, para con una interrupcion de teclado.
        while True:
            fds = self.poll_object.poll()
            # Recorremos la lista atendiendo los eventos.
            for (socket,event) in fds:
                if (event & POLLIN) == POLLIN:
                    self.manejar_pollin (socket)
                elif (event & POLLOUT) == POLLOUT:
                    self.manejar_pollout (socket)
                elif (event & POLLERR) == POLLERR:
                    print "\nProblemas con un cliente."
                    self.terminar_conexion (socket)

    def manejar_pollin(self, socket):
        if socket == self.sckt.fileno():
            # Entonces tenemos a un nuevo cliente con la intención de 
            # conectarse con nosotros. Creamos un nuevo socket para usarlo
            # en la comunicación con el cliente.
            #socket = self.sckt.accept()[0]
            try :
                socket = self.sckt.accept()[0]
            except sckt.error , descripcion:
                codigo_error = descripcion[0]
                if codigo_error==EINVAL : return (ECONNABORTED ,"se aborto la conexion")

            # Construimos un nuevo objeto conexion_cliente.
            self.conexiones_cliente[socket.fileno()] = Conexion_cliente (socket)
            # Registramos los eventos que nos interesan sobre este socket.
            self.poll_object.register (socket, POLLIN | POLLERR)
            print "\n¡Nuevo cliente!. Tenemos "+str(len(self.conexiones_cliente))+\
                                            " clientes conectados."
        else:
            buffer = self.conexiones_cliente[socket].buffer.devolver_mensaje()
            self.conexiones_cliente[socket].buffer.vaciar()
            data = self.conexiones_cliente[socket].recibir_mensaje(False, None)
            if data != None:
                # Al contenido del buffer, le concatenamos lo que acabamos de
                # recibir, y parseamos el resultado. ¿Qué ocurre si el buffer
                # ya contenía un mensaje completo?: referirse a la semántica
                # de la llamada a parse.parsear.
                (comando, codigo_error) = parse.parsear(buffer+data, \
                                        self.conexiones_cliente[socket].buffer)
            else:
                # Ocurrió algún error al intentar leer del socket. Cerramos
                # la conexión.
                self.terminar_conexion (socket)
                # Retornamos de la función ahora mismo, devolviendo un valor
                # por defecto, para asegurarnos de que no se entre por alguno
                # de los condicionales a continuación.
                return -1
            # Estamos por construir un mensaje a enviar al cliente. A partir
            # de ahora, nos interesa también el evento POLLOUT sobre el 
            # socket del cliente.
            self.poll_object.register (socket, POLLOUT | POLLERR)
            if codigo_error == code_ok and not comando == None:
                true_comando = comando.get_command()
                # Construimos el mensaje de confirmación.
                confirmacion = send_confirmation()
                if true_comando == "quit":
                    self.conexiones_cliente[socket].encolar_mensaje (confirmacion)
                    self.conexiones_cliente[socket].set_terminar_conexion (True)
                elif true_comando == "get_file_listing":
                    (code, list) = get_file_listing(self.wd)
                    # Chequeamos el código de error.
                    if code == code_ok:
                        mensaje = confirmacion+list
                        self.conexiones_cliente[socket].encolar_mensaje (mensaje)
                elif true_comando == "get_metadata":
                    archivo = comando.get_argn(1)
                    (code, metadata) = get_metadata(self.wd, archivo)
                    if code == code_ok:
                        mensaje = confirmacion+metadata
                        self.conexiones_cliente[socket].encolar_mensaje (mensaje)
                    elif code == file_not_found:
                        self._send_error(socket, file_not_found)
                else:
                    # Debe ser que se trata de un comando get_slice.
                    archivo = comando.get_argn(1)
                    offset = comando.get_argn(2)
                    length = comando.get_argn(3)
                    (codigo_error, new_offset, new_length, file_name, slice) = get_slice \
                                        (self.wd, archivo, offset, length)
                    if codigo_error == code_ok:
                        mensaje = confirmacion+slice
                        self.conexiones_cliente[socket].encolar_mensaje (mensaje)
                        if new_length > 0:
                            # Entonces, a posterior, tendremos que continuar
                            # extrayendo porciones de el archivo. Memorizamos 
                            # el nombre de el archivo, desde donde debemos 
                            # comenzar a extraer la próxima porción y cuando 
                            # falta enviar.
                            self.conexiones_cliente[socket].set_offset_length_fname \
                                        (new_offset, new_length, file_name)
                        else:
                            # Setemos ciertos valores por defecto, que indican
                            # que se terminó de procesar la petición get_slice.
                            self.conexiones_cliente[socket].set_offset_length_fname \
                                            (-1, -1, "")
                    elif codigo_error == file_not_found:
                        self._send_error(socket, file_not_found)
                    elif codigo_error == bad_offset:
                        self._send_error(socket, bad_offset)
            elif codigo_error != code_ok:
                self._send_error(socket, codigo_error)
                if codigo_error != internal_error:
                    # Contabilizamos la petición incorrecta.
                    self.conexiones_cliente[socket].incr_cant_peticiones_incorrectas ()
                    if self.conexiones_cliente[socket].get_cant_peticiones_incorrectas () == bad_request_limit:
                        self._send_error(socket, bad_request_limit_error)
                    

    def manejar_pollout (self, socket):
        if self.conexiones_cliente[socket].hay_respuestas ():
            codigo = self.conexiones_cliente[socket].enviar_mensaje()
            if  self.conexiones_cliente[socket].get_terminar_conexion () or codigo != 0:
                self.terminar_conexion (socket)
            elif self.conexiones_cliente[socket].get_length () > 0:
                # Obtenemos el nombre del archivo, el largo restante y el 
                # offset desde donde comenzar la próxima porción.
                archivo = self.conexiones_cliente[socket].get_fname ()
                offset = self.conexiones_cliente[socket].get_offset ()
                length = self.conexiones_cliente[socket].get_length ()
                (codigo_error, new_offset, new_length, file_name, slice) = get_slice \
                                        (self.wd, archivo, str(offset), str(length))
                if codigo_error == code_ok:
                    self.conexiones_cliente[socket].encolar_mensaje (slice)
                    if new_length > 0:
                        # Entonces, a posterior, tendremos que continuar
                        # extrayendo porciones de el archivo. Memorizamos 
                        # el nombre de el archivo, desde donde debemos 
                        # comenzar a extraer la próxima porción y cuanto 
                        # falta enviar.
                        self.conexiones_cliente[socket].set_offset_length_fname \
                                        (new_offset, new_length, file_name)
                    else:
                        # Setemos ciertos valores por defecto, que indican
                        # que se terminó de procesar la petición get_slice.
                        self.conexiones_cliente[socket].set_offset_length_fname \
                                            (-1, -1, "")
                elif codigo_error == file_not_found:
                    self._send_error(socket, file_not_found)
                elif res == bad_offset:
                    self._send_error(socket, bad_offset)
        else:
            # Ya no tenemos mensajes para enviarle al cliente. No nos interesa
            # el evento POLLOUT.
            self.poll_object.register (socket, POLLIN | POLLERR)

    def close_server (self):
        """ Al momento de cerrar el servidor, debemos procurar cerrar 
        adecuadamente las conexiones que este mantenía con cada cliente, como
        también cerrar el socket a través del cual escuchaba peticiones de
        conexion. Este es el problema que resolvemos con este método. """
        for key in self.conexiones_cliente:
            # Cerramos la conexión con el cliente.
            self.conexiones_cliente[key].cerrar_conexion()
        
        self.sckt.close()

    def _send_error (self, socket_fd, error_code):
        """ Funcion "privada" de la clase server. Encola un mensaje para el cliente
            con el formato correspondiente según el código de error """

        answer = str(error_code) + " " + error_messages[error_code] + eol       
        self.conexiones_cliente[socket_fd].encolar_mensaje (answer)

        if fatal_status(error_code):
             # Indicamos que pretendemos que la conexion se cierre, luego.
             self.conexiones_cliente[socket_fd].set_terminar_conexion (True)

    def terminar_conexion (self, socket_fd):
        # Des-registramos el socket.
        self.poll_object.unregister (socket_fd)
        # Cerramos la conexión con el cliente.
        self.conexiones_cliente[socket_fd].cerrar_conexion()
        # Eliminamos su correspondiente objeto conexion_cliente.
        del self.conexiones_cliente[socket_fd]
        print "\nCerramos conexión con un cliente. Tenemos "+\
                    str(len(self.conexiones_cliente))+" clientes conectados."

def main():
    """
    Interfaz interactiva para el servidor. Permite elegir el directorio y
    el puerto. Para salir presionar CTRL+d
    """

    cwd = os.getcwd()
    # Parsear argumentos
    parser = optparse.OptionParser(usage="%prog [options]")
    parser.add_option("-d", "--directorio",
                      help=u"Directorio a publicar por el server", default=cwd)
    parser.add_option("-p", "--port",
                      help=u"Puerto donde escuchar", default=default_port)

    options, args = parser.parse_args()
    try:
        port = int(options.port)
        directorio = str(options.directorio)
        directorio=cwd+"/"+directorio
        print directorio
    except ValueError:
        sys.stderr.write("Número de puerto inválido: %s\n" % str(options.port)) 
        parser.print_help()
        sys.exit(1)

    server = Server(directorio, port)
    try:
        server.server_run()   

    except KeyboardInterrupt:
        sys.stderr.write("Desconectando por voluntad propia!\n")
        server.close_server()
        sys.exit(0)

if __name__ == '__main__':
    main()
